from flask import Blueprint,render_template,redirect,url_for,flash,request,jsonify,g
from app.forms import PublishArticleForm
from flask_login import login_required,current_user
from app.models import Article,ArticleType,User
from app.extensions import db
from datetime import timedelta

article=Blueprint('article',__name__) # 创建蓝本对象，第一个参数是蓝本名，不能与其他蓝本名重复；第二个参数是要导入蓝本的模块的名；第三个参数是统一给蓝本里路由添加的前缀，可不写


@article.route('/publish_article1/',methods=['GET','POST'])
@login_required # 保护当前路由，需登录才能访问。详细注释在user.py中
def publish_article1():
    form=PublishArticleForm()
    if form.validate_on_submit():
        u=current_user._get_current_object() # 获取原始用户，类似mail.py中current_app._get_current_object()
        title=form.title.data
        content=form.content.data
        type_id=request.form.get('article_type') # 取option标签的value属性的值
        if int(type_id)==0: # 定义0表示未选择文章类型
            flash('请选择文章类型')
            return redirect(url_for('article.publish_article1'))
        a=Article(title=title,content=content,user=u,type_id=int(type_id)) # user表示作者，是user表里设的名字，用于根据文章反向找作者
        db.session.add(a)
        flash('发布成功')
        return redirect(url_for('article.all_article'))
    # 一开始写的时候没有文章类型，所以PublishArticleForm()中没有下拉列表(SelectField)。后来写了文章类型，由于下拉列表的内容要查数据库才能显示，所以PublishArticleForm()中不再添加下拉列表了，而是在前端页面中手动添加下拉列表，在这里查数据库再传给前端
    article_type=ArticleType.query.all()
    if not article_type:
        article_type=["未添加任何'文章类型'"]
        flash("未添加任何'文章类型'，请先手动添加一些'文章类型'到数据库，再发布文章。暂不支持前端发布文章时顺便新建文章分类")
    return render_template('article/publish_article1.html',form=form,article_type=article_type)


@article.route('/publish_article2/',methods=['GET','POST']) # 已用钩子函数设置需登录才能访问，将当前路由添加到了user.py中required_login_list列表中
def publish_article2():
    if request.method=='POST':
        comment_id=request.form.get('comment_id','0') # 发布评论时用，是被评论的文章的id，发布文章时获取不到(没传)返回为0
        title=request.form.get('title',None) # 发布评论时没传title，因评论没有title，所以加上获取不到时返回None
        content=request.form['mytextarea']
        uid=g.user.id # user.py中有g.user注释
        type_id=request.form.get('article_type') # 取option标签的value属性的值
        if int(type_id)==0: # 定义0表示未选择文章类型
            flash('请选择文章类型')
            return redirect(url_for('article.publish_article2'))
        a=Article(title=title,content=content,uid=uid,type_id=int(type_id),comment_id=int(comment_id))
        db.session.add(a)
        flash('发布成功')
        if a.comment_id!=0 or title is None: # 由于文章和评论共用一个模型，且发布评论也用的这个路由，所以需判断发布的是文章还是评论。设计模型时定义了comment_id为0是文章，且发布评论时没传title，所以这两点都可以判断
            return redirect(url_for('article.detail')+'?aid='+comment_id)
        return redirect(url_for('article.all_article'))
    article_type=ArticleType.query.all()
    if not article_type:
        article_type=["未添加任何'文章类型'"]
        flash("未添加任何'文章类型'，请先手动添加一些'文章类型'到数据库，再发布文章。暂不支持前端发布文章时顺便新建文章分类")
    return render_template('article/publish_article2.html',article_type=article_type)


@article.route('/collected_or_liked/<article_id>',methods=['GET','POST']) # all_article.html中Ajax请求使用，与detail.html中的发送请求的传参方式不同
@login_required
def collected_or_liked(article_id):
    if current_user.is_collected_or_liked(article_id):
        num=current_user.del_collect_or_like(article_id)
        return jsonify(number=num)
    else:
        num=current_user.add_collect_or_like(article_id)
        return jsonify(number=num)


@article.route('/collected_or_liked2/',methods=['GET','POST']) # detail.html中Ajax请求使用，与all_article.html中的发送请求的传参方式不同
@login_required
def collected_or_liked2():
    article_id=request.form['article_id'] # 注意返回的是字符串类型
    if current_user.is_collected_or_liked(int(article_id)):
        num=current_user.del_collect_or_like(article_id)
        return jsonify(number=num)
    else:
        num=current_user.add_collect_or_like(int(article_id))
        return jsonify(number=num)


@article.route('/all_article/') # 显示所有文章
def all_article():
    '''不分页查询，显示所有结果
    a=Article.query.filter(Article.comment_id==0).order_by(Article.timestamp.desc()).all() # 获取已发布的文章，并按发布时间降序排列，即刚发布的文章显示在最上面
    time_list=[]
    showed_article=[] # 从a中过滤掉已注销的用户的文章，将未注销的用户的文章放在这里
    for i in a:
        if i.user.is_deleted is False: # 不显示已注销的用户的文章
            showed_article.append(i)
            time_list.append(i.timestamp+timedelta(hours=8)) # 数据库里是utc时间(Article模型里设置的)，比东八区晚8小时，前端显示时要显示8小时后的时间。将加过8小时的时间放在列表里。若前端用moment().format()，则传值时不用加8
    d=dict(zip(showed_article,time_list)) # showed_article和time_list是可迭代的对象，zip将可迭代的对象作为参数，将对象中对应的元素打包成一个个元组，然后返回由这些元组组成的对象。再转为字典，每一个键对应showed_article中一个元素(一篇文章)，每一个值对应time_list中一个元素(对应文章的发布时间戳)
    # a=[1,2,3]; b=[4,5,6]; zip(a,b)的结果类似[(1,4),(2,5),(3,6)]，但不是它，而是一个zip对象，再转为字典是{1:4,2:5,3:6}
    return render_template('article/all_article.html',form=form,d=d)
    '''
    '''分页显示所有用户的文章，包括已注销的用户
    page=request.args.get('page',1,type=int) # 没有获取到时返回1，并将返回值类型设为int，默认str，后面传page时应传入数字而不是字符串
    pagination=Article.query.filter(Article.comment_id==0).order_by(Article.timestamp.desc()).paginate(page=page,per_page=2,error_out=False) # 返回Pagination对象，包含所有分页信息
    # page(必传)是要查的页码,即想看哪一页;per_page是每页显示的条数,默认20条;error_out是查询出错时(如共3页但想看第10页)是否报错404,默认True
    # Pagination对象的属性
    # items:当页的数据;page:当前页码;pages:总页码数;per_page:每页条数;total:总记录数;prev_num:上一页的页码;next_num:下一页的页码;has_prev:是否有上一页;has_next:是否有下一页
    # Pagination对象的方法
    # prev:上一页的分页对象;next:下一页的分页对象;iter_pages:是一个迭代器，返回的是分页导航条上显示的所有页码，显示不完返回None
    a=pagination.items # 当前页的数据
    time_list=[]
    for i in a:
        time_list.append(i.timestamp+timedelta(hours=8))
    d=dict(zip(a,time_list))
    return render_template('article/all_article.html',d=d,pagination=pagination)
    '''
    '''下面的写法，能查到所有未注销的用户的文章，但分页显示有问题，已注销的用户的文章，虽然不显示出来，但占页。因过滤掉已注销的用户的文章是在生成分页对象之后，所以有这个问题。
    page=request.args.get('page',1,type=int)
    pagination=Article.query.filter(Article.comment_id==0).order_by(Article.timestamp.desc()).paginate(page=page,per_page=2,error_out=False)
    a=pagination.items
    time_list=[]
    showed_article=[]
    for i in a:
        if i.user.is_deleted is False:
            showed_article.append(i)
            time_list.append(i.timestamp+timedelta(hours=8))
    d=dict(zip(showed_article,time_list))
    return render_template('article/all_article.html',d=d,pagination=pagination)
    '''
    # 分页显示所有用户的文章，不包括已注销的用户
    # 用join多表联合查询，from
    # https://blog.csdn.net/m0_38076876/article/details/109057195
    # https://www.jianshu.com/p/f756fd8ea1cc
    # https://www.jianshu.com/p/2c6c76f94b88
    # https://blog.csdn.net/li944254211/article/details/109330531
    page=request.args.get('page',1,type=int)
    pagination=Article.query.join(User,User.id==Article.uid).filter(Article.comment_id==0,User.is_deleted==False).order_by(Article.timestamp.desc()).paginate(page=page,per_page=2,error_out=False) # 因Article模型中添加了User模型的id字段作为外键，所以通过这点将User表和Article表关联起来再查询，关联即join(User,User.id==Article.uid)
    a=pagination.items
    time_list=[]
    for i in a:
        time_list.append(i.timestamp+timedelta(hours=8))
    d=dict(zip(a,time_list))
    return render_template('article/all_article.html',d=d,pagination=pagination)


@article.route('/detail/') # 显示单篇文章的内容
def detail():
    article_id=request.args.get('aid') # 注意返回的是字符串类型，数据库里是int，但下一行的查询不转int也能查到
    article=Article.query.get(int(article_id))
    if article:
        if article.user.is_deleted is True: # 防止访问已注销的用户的文章
            flash('该用户已注销')
            return redirect(url_for('article.all_article'))
        article.click_times+=1 # 数据库里增加阅读量
        page=request.args.get('page',1,type=int)
        pagination=Article.query.filter(article.id==Article.comment_id).order_by(Article.timestamp.desc()).paginate(page=page,per_page=2,error_out=False) # 当前文章的所有评论
        return render_template('article/detail.html',article=article,pagination=pagination)
    else:
        flash('您访问的文章不存在')
        return redirect(url_for('article.all_article'))


@article.app_template_filter('myself_trim') # 自定义过滤器，注释在all_article.html中
def list_reverse(string):
    if len(string)>150:
        return string[:150]+'...' # 取传来的内容的前150个字符并返回。防止all_article.html中单篇文章的内容太多，全显示时太占位置，所以只显示前150个字符
    return string


@article.app_template_filter('show_time')
def show(t):
    return (t+timedelta(hours=8)).strftime('%Y-%m-%d %H:%M:%S') # 之前all_article路由里是给模版文件传加过8的时间，或detail.html里传数据库时间用moment().format()显示本地时间，学了自定义过滤器后也可以直接传数据库里的时间，模版文件渲染时再加8


@article.route('/delete_article/')
@login_required
def delete_article():
    article_id=request.args.get('aid') # 注意返回的是字符串类型，数据库里是int，但下一行的查询不转int也能查到
    article=Article.query.get(int(article_id))
    if article:
        if article.user.id==current_user.id:
            comments=Article.query.filter(Article.comment_id==article.id).all()
            if comments:
                for i in comments:
                    db.session.delete(i) # 删除文章的评论
            db.session.delete(article)
            db.session.commit()
            flash('文章删除成功')
            return redirect(url_for('article.all_article'))
    flash('已终止非法的删除操作，文章删除失败')
    return redirect(url_for('article.all_article'))


@article.route('/search_by_type/')
def search_by_type():
    type_id=request.args.get('type_id')
    type=ArticleType.query.get(type_id)
    if type:
        page=request.args.get('page',1,type=int)
        pagination=Article.query.filter(Article.type_id==type_id,Article.comment_id==0).order_by(Article.timestamp.desc()).paginate(page=page,per_page=2,error_out=False) # 可直接给模版文件传type，再type.articles就能获得该类型的所有文章对象，但由于文章和评论共用一个模型，所以这样还会获得评论，所以写这句查文章
        return render_template('article/search_by_type.html',type=type,pagination=pagination)
    else:
        flash('文章类型不存在')
        return redirect(url_for('main.index'))


@article.route('/search_by_user/')
def search_by_user():
    user_id=request.args.get('user_id')
    user=User.query.get(user_id)
    if user:
        page=request.args.get('page',1,type=int)
        pagination=Article.query.filter(Article.uid==user_id,Article.comment_id==0).order_by(Article.timestamp.desc()).paginate(page=page,per_page=2,error_out=False)
        return render_template('article/search_by_user.html',user=user,pagination=pagination)
    else:
        flash('用户不存在')
        return redirect(url_for('main.index'))